﻿using System;
using System.Collections;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using ATMCableCloud.CableCloudExceptions;
using ATMConst;

namespace ATMCableCloud
{
	abstract class BasicTcpServer
	{
		internal class Connection
		{
			public byte[] buffer { get; set; }
			public Socket socket { get; private set; }
			public ArrayList allBytes_ = new ArrayList();
			public Connection(Socket socket,int size)
			{
				buffer = new byte[size];
				this.socket = socket;
			}
		}
		public int serializedCellPackageSize { get;protected set; }
		protected delegate void PackageHandling(Package.Package package,
										IAsyncResult asyncResult,
										Socket socket);
		protected delegate void acceptingHandler(IAsyncResult ar);
		protected abstract void peerDisconnectionHandler(Socket socket);
		protected AsyncCallback acceptingHandler_;
		public Socket socket { get; protected set; }
		public int port { get; protected set; }
		protected PackageHandling mainTcpPackageHandler { get; set; }
		public int maxPeers { get; private set; }
		protected ManualResetEvent allDone = new ManualResetEvent(false);
		protected ManualResetEvent allReaded = new ManualResetEvent(false);
		protected BasicTcpServer(IPAddress remoteIpAddress,
							int port,
							int maxPeers,
							AsyncCallback handler = null)
		{
			acceptingHandler_ = acceptingHandler_ == null ? acceptCallback : handler;
			socket = new Socket(remoteIpAddress.AddressFamily,
									SocketType.Stream,
									ProtocolType.Tcp);
			this.port = port;
			this.maxPeers = maxPeers;
			socket.Bind(new IPEndPoint(remoteIpAddress, this.port));
			serializedCellPackageSize = Const.BUFFER_SIZE;
		}
		protected void acceptCallback(IAsyncResult ar)
		{
			Socket handler;
			try
			{
				handler = getConnectedSocket(ar);
			}
			catch (ClientDisconnectedException)
			{
				allDone.Set();
				return;
			}
			allDone.Set();
			if (handler == null)
				return;
			// Signal the main thread to continue.
			var package = receivePackage(handler);
			if (package == null)
				return;
			mainTcpPackageHandler(	package,
									ar,
									handler);
		}
		protected Socket getConnectedSocket(IAsyncResult ar)
		{
			try
			{
				var listener = (Socket)ar.AsyncState;
				return listener.EndAccept(ar);
			}
			catch (ObjectDisposedException)
			{
				return null;
			}
		}
		public Package.Package receivePackage(Socket handler)
		{
			try
			{
				var connection = new Connection(handler, serializedCellPackageSize);
				allReaded.Reset();
				handler.BeginReceive(connection.buffer,
				                    0,
									serializedCellPackageSize,
				                    0,
				                    readCallback,
				                    connection);
				allReaded.WaitOne();
				var bytes = new byte[connection.allBytes_.Count];
				for (int i = 0; i < bytes.Length; i++)
					bytes[i] = (byte) connection.allBytes_[i];
                Console.WriteLine("WTF");
                foreach (byte b in bytes)
                    Console.Write((char)b);
                Console.WriteLine("");
				return Package.Package.deserializePackage(bytes);
			}
			catch (NullReferenceException)
			{
				return null;
			}
		}
		protected void readCallback(IAsyncResult ar)
		{
			var state = ar.AsyncState as Connection;
			var handler = state.socket;
			try
			{
				Console.WriteLine("read");
				var read = handler.EndReceive(ar);
				if (read > 0)
				{
					Console.WriteLine(read);
					for (var i = 0; i < read; i++)
						state.allBytes_.Add(state.buffer[i]);
					var bytes = new byte[state.allBytes_.Count];
					for (int i = 0; i < bytes.Length; i++)
						bytes[i] = (byte) state.allBytes_[i];
					try
					{
						Package.Package package = Package.Package.deserializePackage(bytes);
						if (package != null)
						{
							allReaded.Set();
							return;
						}
					}
					catch (System.Runtime.Serialization.SerializationException e)
					{
					}
					if (read == serializedCellPackageSize)
					{
						Console.WriteLine("FULL");
						AsyncCallback callback = readCallback;
						if(handler.Available > 0)
							handler.BeginReceive(state.buffer,
						                     0,
						                     serializedCellPackageSize,
						                     0,
											 callback,
						                     state);
						else
							allReaded.Set();
					}
					else
					{
						Console.WriteLine("NotFull");
						allReaded.Set();
					}
				}
				else
				{
					Console.WriteLine("Read<=0");
					allReaded.Set();
				}
			}
			catch (SocketException e)
			{
				Console.WriteLine(e);
				//allReaded.Set();
				peerDisconnectionHandler(handler);
			}
		}
		public void sendPackage(Package.Package package, Socket handler)
		{
			var bytes = Package.Package.serializePackage(package);
			if(handler != null && handler.Connected)
			handler.BeginSend(bytes,
								0,
								bytes.Length,
								SocketFlags.None,
								sendCallback,
								handler);
		}
		static void sendCallback(IAsyncResult ar)
		{
			var handler = ar.AsyncState as Socket;
			handler.EndSend(ar);
		}
		public void start()
		{
			socket.Listen(maxPeers);
			while (true)
			{
				allDone.Reset();
				socket.BeginAccept(acceptingHandler_, socket);
				allDone.WaitOne();
			}
		}
    }
}
